/*
 * SPACE INVADERS!
 *
 * This is a simple "space invaders" type game in which you move your "gun"
 * back and forth to shoot at invaders who are decending from above. Above
 * the gun are "shields" which protect you from alien fire, but disintegrate
 * a little with each hit.
 *
 * Both you and the invaders can have up to 5 concurrent shots visible on the
 * screen. Your gun holds 5 shots, and reloads one shot every 10 "ticks" of
 * the game. (Each tick is one movement of the invaders).
 *
 * Use the LEFT and RIGHT arrow keys to move your gun. SPACE fires. ESCAPE
 * quits the game.
 *
 * Copyright 1994-2003 Dave Dunfield
 * All rights reserved.
 *
 * Permission granted for personal (non-commercial) use only.
 *
 * Compile command: cc invader -fop
 */
#include <stdio.h>
#include <video.h>

/* Misc. game parameters */
#define	MAX_X		24		/* Maximum horizontal movement */
#define	SHIELD_LINE	23		/* Shield is on this line */
#define	PLAY_WIDTH	7		/* Width of player "gun" */
#define	MAX_SHOT	5		/* Number of concurrent shots (each side) */
#define	INVADERS	24		/* Number of invaders */
#define	RELOAD		10		/* Reload frequency */
#define	SHOTS		5		/* Gun holds this many shots */

/* PC graphic characters */
#define	SHOT		4		/* Shot in progress */
#define	SHIELD1		0xB0	/* Really weak shield */
#define	SHIELD2		0xB1	/* Somewhat weak shield */
#define	SHIELD3		0xB2	/* Fairly strong shield */
#define	SHIELD4		0xDB	/* Really strong shield */

/* Sound frequencies */
#define	SKILL		70		/* Invader dies */
#define	SISHOT		250		/* Invader shoots */
#define	SPSHOT		350		/* Player shoots */
#define	SSHIELD		100		/* Shield gets hit */
#define	SMOVE		9000	/* Invaders are moving */

/* Melodies for winning and dieing */
unsigned Swin[] = { 100, 200, 300, 400, 500, 0 };
unsigned Sdie[] = { 500, 400, 300, 200, 100, 0 };

/* ASCII graphics of Invaders - view 1 */
char invaders1[3][3][8] = {
	" \\^^^/ ",
	" <o$o> ",
	" \\vvv/ ",

	" <=%=< ",
	" <$:$- ",
	" <=%=< ",

	" O---O ",
	" ):: ) ",
	" O---O " }

/* ASCII graphics of Invaders - view 2 */
char invaders2[3][3][8] = {
	" /^^^\\ ",
	" <o$o> ",
	" /vvv\\ ",

	" >=%=> ",
	" -$:$> ",
	" >=%=> ",

	" O---O ",
	" ( ::( ",
	" O---O " }

/* Status of invaders 0 = Alive, !0 = Dead */
char invader_status[8][3];

/* Status of player shield 0=None ... 4=Full strength */
char shield[80] = {
	0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0 };

/* Characters to depict varying shield strengths */
char shield_graphic[] = { ' ', SHIELD1, SHIELD2, SHIELD3, SHIELD4 };

/* Active shots .. Hold X-Y coordinates of bullets in motion */
int invader_shots[MAX_SHOT][2], int player_shots[MAX_SHOT][2];

int	X = 0,				/* X position of invader matrix */
	Y = 0,				/* Y position of invader matrix */
	player = 35,		/* X position of players gun */
	shots = SHOTS,		/* Number of shots in gun */
	killed = 0;			/* Number of invaders killed */

main()
{
	int i;
	static int reload = RELOAD;

	vopen();
	vcursor_off();
	draw_invaders(invaders1);
	draw_player();

	for(;;) {
		if(random(100) < 20)
			invader_shot();
		for(i=0; i < 5; ++i) {
			update_shots();
			move_player();
			delay(50); }
		sound(SMOVE);
		vgotoxy(0, SHIELD_LINE);
		for(i=0; i < sizeof(shield); ++i)
			vputc(shield_graphic[shield[i]]);
		move_invaders();
		if(!--reload) {
			reload = RELOAD;
			if(shots < SHOTS)
				++shots; } }
}

/*
 * Exit program with message
 */
terminate(unsigned *tune, char *msg)
{
	vclscr();
	vputs(msg);
	while(*tune) {
		sound(*tune++);
		delay(250); }
	vcursor_line();
	while(vtstc());
	sound_off();
	exit(0);
}

/*
 * Draw the invader matrix
 */
draw_invaders(char invaders[3][3][8])
{
	int i, j, k, x;

	for(i=0; i < 8; ++i) {
		x = (i*7) + X;
		for(j=0; j < 3; ++j) {
			if((k = (j*4)+Y) < SHIELD_LINE) {
				vgotoxy(x, k);
				vputs("       "); }
			if(!invader_status[i][j]) {
				if(k >= (SHIELD_LINE-3))
					terminate(Sdie, "The aliens have landed!");
				vgotoxy(x, k+1);
				vputs(invaders[j][0]);
				vgotoxy(x, k+2);
				vputs(invaders[j][1]);
				vgotoxy(x, k+3);
				vputs(invaders[j][2]); } } }
}

/*
 * Move the invaders
 */
move_invaders()
{
	static char direction = 0, toggle = 0;

	draw_invaders((++toggle & 1) ? invaders2 : invaders1);

	if(direction) {
		if(--X < 0) {
			X = 0;
			++Y;
			direction = 0; } }
	else {
		if(++X >= MAX_X) {
			X = MAX_X - 1;
			++Y;
			direction = -1; } }
	sound_off();
}

/*
 * Make an invader take a shot
 */
invader_shot()
{
	int i, j, x;

	/* Locate an empty slot in the shot tracking array */
	for(i=0; i < MAX_SHOT; ++i)
		if(!invader_shots[i][0])
			goto free1;
	return;

free1:
	/* Locate a low invader to make the shot */
	sound(SISHOT);
	x = random(8);
	for(;;) {
		j = 3;
		while(--j >= 0)
			if(!invader_status[x][j])
				goto free2;
		x = (x + 1) % 8; }

free2:
	invader_shots[i][0] = (j*4) + Y + 2;
	invader_shots[i][1] = (x * 7) + X + 3;
}

/*
 * Update the positions of the shots on the screen
 */
update_shots()
{
	int i, j, k, x, y, x1, y1;

	for(i=0; i < MAX_SHOT; ++i) {
		/* Update pending invader shot */
		if(invader_shots[i][0]) {
			vgotoxy(x=invader_shots[i][1], y=invader_shots[i][0]++);
			vputc(' ');
			if(y < SHIELD_LINE) {
				vgotoxy(x, ++y);
				vputc(SHOT); }
			else if(y == SHIELD_LINE) {
				if(shield[x]) {
					sound(SSHIELD);
					--shield[x];
					invader_shots[i][0] = 0; } }
			else if(y > SHIELD_LINE) {
				invader_shots[i][0] = 0;
				if((x > player) && (x <= (player+PLAY_WIDTH)))
					terminate(Sdie, "You died!"); } }

		/* Update pending player shot */
		if(player_shots[i][0]) {
			vgotoxy(x = player_shots[i][1], y=player_shots[i][0]--);
			vputc(' ');
			if(--y) {
				vgotoxy(x, y);
				vputc(SHOT); }
			for(j=0; j < 8; ++j) {
				x1 = (j*7) + X;
				if((x1 > x) || ((x1+6) < x))
					continue;
				for(k=0; k < 3; ++k) {
					if(invader_status[j][k])
						continue;
					y1 = (k*4) + Y;
					if(((y1+1) > y) || ((y1+4) < y))
						continue;
					vgotoxy(x, y);
					vputc(' ');
					player_shots[i][0] = 0;
					kill_invader(j, k); } } } }
					
}

/*
 * Terminate an invader
 */
kill_invader(int x, int y)
{
	int i;

	sound(SKILL);
	invader_status[x][y] = -1;		/* Record kill */
	x = (x*7)+X;
	for(i=0; i < 4; ++i) {			/* Erase from screen */
		vgotoxy(x, (y*4)+Y+i);
		vputs("        "); }
	if(++killed >= INVADERS)		/* Check for victory */
		terminate(Swin, "You WIN!");
}

/*
 * Handle player moves
 */
move_player()
{
	int i, j;

	switch(vtstc()) {
		case _KLA :					/* Move LEFT */
			if(player)
				--player;
			draw_player();
			return;
		case _KRA :					/* Move RIGHT */
			if(player < (80-(PLAY_WIDTH+3)))
				++player;
			draw_player();
			return;
		case 0x1B :					/* Quit game */
			terminate(Sdie, "Chicken!");	
		case ' ' :					/* Fire cannon */
			if(shots) {
				for(i=0; i < MAX_SHOT; ++i)
					if(!player_shots[i][0]) {
						sound(SPSHOT);
						--shots;
						if(shield[j = player+((PLAY_WIDTH/2)+1)])
							--shield[j];
						else {
							player_shots[i][0] = SHIELD_LINE;
							player_shots[i][1] = j; }
						return; } } }
}

/*
 * Draw the player at his current position
 */
draw_player()
{
	vgotoxy(player, SHIELD_LINE+1);
	vputs(" ***^*** ");
}
